﻿using IOP.OpenType.Tables;
using IOP.OpenType;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace IOP.Codec.OpenType
{
    /// <summary>
    /// 
    /// </summary>
    public class CmapTableLoader
    {
        /// <summary>
        /// 加载
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="entry"></param>
        /// <returns></returns>
        public async Task<CmapTable> Load(FileStream stream, TableEntry entry)
        {
            uint offset = entry.Offset;
            stream.Seek(offset, SeekOrigin.Begin);
            var pipe = new Pipe();
            var w = WriteToPipe(stream, pipe.Writer, entry);
            var r = ReadFromPipe(pipe.Reader, entry);
            await Task.WhenAll(w, r);
            CmapTable table = r.Result;
            return table;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private async Task WriteToPipe(Stream stream, PipeWriter writer, TableEntry entry)
        {
            try
            {
                long already = 0;
                while (true)
                {
                    Memory<byte> memory = writer.GetMemory(2048);
                    int bytesRead = await stream.ReadAsync(memory);
                    writer.Advance(bytesRead);
                    already += bytesRead;
                    if (bytesRead == 0 || already >= entry.Length) break;
                    FlushResult result = await writer.FlushAsync();
                    if (result.IsCompleted) break;
                }
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                writer.Complete();
            }
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private async Task<CmapTable> ReadFromPipe(PipeReader reader, TableEntry entry)
        {
            try
            {
                CmapTable table = new CmapTable();
                table.Entry = entry;
                long finalBytes = entry.Length;
                long byteIndex = 0;
                int index = 0;
                int size = Marshal.SizeOf<EncodingRecord>();
                ReadResult result = await reader.ReadAsync();
                ReadOnlySequence<byte> buffer = result.Buffer;
                ReadHead(ref buffer, table);
                int headerBytes = 4 + size * table.NumTables;
                byteIndex += 4;
                while (true)
                {
                    if(buffer.Length < headerBytes)
                    {
                        result = await reader.ReadAsync();
                        buffer = result.Buffer;
                    }
                    ReadEncodingRecord(ref buffer, ref index, ref byteIndex, table);
                    if (byteIndex >= headerBytes) break;
                }
                index = 0;
                byteIndex = 0;
                table.SubTables = new CmapSubTable[table.NumTables];
                while (index < table.NumTables)
                {
                    EncodingRecord encoding = table.EncodingRecords[index];
                    (ushort format, ushort length, ushort language) = ReadSubTableHeader(ref buffer, encoding, ref byteIndex);
                    if (byteIndex - 6 + length > entry.Length) throw new ArgumentOutOfRangeException();
                    if(buffer.Length < byteIndex + length)
                    {
                        result = await reader.ReadAsync();
                        buffer = result.Buffer;
                    }
                    switch (format)
                    {
                        case 4:
                            var subTable = ReadSubTable4(ref buffer, ref byteIndex, encoding, format, length, language);
                            table.SubTables[index++] = subTable;
                            break;
                        default:
                            index++;
                            break;
                    }
                }
                return table;
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                reader.Complete();
            }
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void ReadHead(ref ReadOnlySequence<byte> buffer, CmapTable table)
        {
            SequenceReader<byte> reader = new SequenceReader<byte>(buffer);
            reader.TryReadBigEndian(out short version);
            reader.TryReadBigEndian(out short numTables);
            table.Version = (ushort)version;
            table.NumTables = (ushort)numTables;
            table.EncodingRecords = new EncodingRecord[table.NumTables];
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void ReadEncodingRecord(ref ReadOnlySequence<byte> buffer, ref int index, ref long byteIndex, CmapTable table)
        {
            SequenceReader<byte> reader = new SequenceReader<byte>(buffer);
            reader.Advance(byteIndex);
            int size = Marshal.SizeOf<EncodingRecord>();
            while (reader.Remaining >= size)
            {
                EncodingRecord record = ReadEncodingRecord(ref reader);
                table.EncodingRecords[index++] = record;
                byteIndex += size;
                if (index >= table.NumTables)
                    break;
            }
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private EncodingRecord ReadEncodingRecord(ref SequenceReader<byte> reader)
        {
            reader.TryReadBigEndian(out short pId);
            reader.TryReadBigEndian(out short eId);
            reader.TryReadBigEndian(out int offset);
            return new EncodingRecord
            {
                PlatformID = (ushort)pId,
                EncodingID = (ushort)eId,
                Offset = offset
            };
        }
        /// <summary>
        /// 读取子表头属性
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="byteIndex"></param>
        /// <param name="record"></param>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private (ushort, ushort, ushort) ReadSubTableHeader(ref ReadOnlySequence<byte> buffer, EncodingRecord record, ref long byteIndex)
        {
            byteIndex = (uint)record.Offset;
            SequenceReader<byte> reader = new SequenceReader<byte>(buffer);
            reader.Advance(byteIndex);
            reader.TryReadBigEndian(out short format);
            reader.TryReadBigEndian(out short length);
            reader.TryReadBigEndian(out short language);
            byteIndex += 6;
            return ((ushort)format, (ushort)length, (ushort)language);
        }
        /// <summary>
        /// 创建格式4子表
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="byteIndex"></param>
        /// <param name="format"></param>
        /// <param name="length"></param>
        /// <param name="language"></param>
        /// <param name="record"></param>
        /// <returns></returns>
        private CmapSubTable4 ReadSubTable4(ref ReadOnlySequence<byte> buffer, ref long byteIndex, EncodingRecord record, ushort format, ushort length, ushort language)
        {
            long end = length + (uint)record.Offset;
            SequenceReader<byte> reader = new SequenceReader<byte>(buffer);
            reader.Advance(byteIndex);
            CmapSubTable4 subTable4 = new CmapSubTable4();
            subTable4.Encoding = record;
            subTable4.Format = format;
            subTable4.Length = length;
            subTable4.Language = language;
            reader.TryReadBigEndian(out short segCountX2);
            reader.TryReadBigEndian(out short searchRange);
            reader.TryReadBigEndian(out short entrySelector);
            reader.TryReadBigEndian(out short rangeShift);
            subTable4.SegCountX2 = (ushort)segCountX2;
            subTable4.SearchRange = (ushort)searchRange;
            subTable4.EntrySelector = (ushort)entrySelector;
            subTable4.RangeShift = (ushort)rangeShift;
            ushort segCount = (ushort)((ushort)segCountX2 / 2);
            ushort[] endCode = new ushort[segCount];
            for (int i = 0; i < segCount; i++)
            {
                reader.TryReadBigEndian(out short local);
                endCode[i] = (ushort)local;
            }
            subTable4.EndCode = endCode;
            reader.Advance(2);
            ushort[] startCode = new ushort[segCount];
            for(int i = 0; i < segCount; i++)
            {
                reader.TryReadBigEndian(out short local);
                startCode[i] = (ushort)local;
            }
            subTable4.StartCode = startCode;
            short[] idDelta = new short[segCount];
            for (int i = 0; i < segCount; i++)
            {
                reader.TryReadBigEndian(out short local);
                idDelta[i] = local;
            }
            subTable4.IdDelta = idDelta;

            long remind = end - reader.Consumed;
            long count = remind / sizeof(ushort);
            ushort[] idRangeOffset = new ushort[count];
            for(int i = 0; i < count; i++)
            {
                reader.TryReadBigEndian(out short local);
                idRangeOffset[i] = (ushort)local;
            }
            subTable4.IdRangeOffset = idRangeOffset;
            byteIndex += length - byteIndex;
            return subTable4;
        }
    }
}
